Google-zx 是谷歌推出的一个开源的项目, 一个可以使用前端熟悉的JavaScript
语法来编写 shell
的工具。如:
1 | await $`cat package.json | grep name` |
上面代码中可以看出在 JavaScript
中 插入了 shell
。那么这种写法行不行呢? 答案是可以的,如果我们使用了Google-zx 的话。
问题背景
创建 shell
脚本的主要目的是 shell
脚本能够帮忙我们自动化实现一些重复的任务的。在编写脚本的时候通常会选择一些更加方便的编程预言。 对于前端开发工程师来说 Node.js
无疑是最好的选择了,它提供了很多核心的模块,并且还可以导入前端其他的脚本库。可以降低很多的学习成本。
但是我们尝试编写一个在Node.js
下运行的 shell
脚本,发现并不是很流畅。 需要为子进程编写特殊处理;注意转义命令行参数;然后使用使用标准输出 stdout
和错误 stderr
,显得不是很直观; 并且在使用之前需要做很多额外的操作,如:引入库等。
1 | // 引入 execSync 命令 from child_process 模块 |
1 | // 引入 exec 命令 from child_process 模块 |
Bash shell
脚本语言试试编写shell
脚本的最佳选择,不需要编写代码来处理子进程,并且它有用于处理
stdout
和 stderr
的内置语言特性。 但是Bash
编写 shell
脚本也不是那么容易。语法比较混乱,使得实现逻辑或处理用户提示是输入之类的事情变得不是那么方便。
那么Google
的zx.js
库就很有助于使用Node.js
高效快速的编写shell
脚本。
安装
可根据如下命令进行全局安装:
1 | npm i -g zx |
不一定非得全局安装, 可以局部安装的。下面的例子,都是使用的全局安装
目前需要的环境
1 | Node.js >= 16.0.0 |
具体的
Node
环境最新可到 Google/zx查看。
使用
安装好 zx
之后,有两种方式可以编写脚本:
- 将
shell
脚本编写在后缀名为.mjs
的文件中, 不需要额外的封装了 - 将
shell
脚本编写在后缀名为.js
的文件中,这种方式需要使用如下方式对脚本进行封装
1 | void async function() { |
其次需要在文件头添加如下脚本:
1 |
运行的时候如果是全局安装的情况下直接在命令行使用zx <文件>
即可
1 | $ zx ./src/index.mjs |
举个例子:
1 |
|
内置函数
$`command`
该命令主要是使用了 child_process
的 spawn
( child_process.spawn ) 函数来执行指定的字符串,并返回一个ProcessPromise
。如果执行的程序返回非零退出代码。 将抛出ProcessOutput
。
举个例子:
1 | // 命令 |
ProcessPromise
返回的 ProcessPromise
的 typescript
接口定义为:
1 | class ProcessPromise<T> extends Promise<T> { |
其中pipe()
方法可用于重定向标准输出:
1 | await $`echo "你好世界"` |
结果如下:
关于更多的管道例子
ProcessOutput
如果执行的程序返回非零退出代码,ProcessOutput
将被抛出。ProcessPromise
的typescript
定义为:
1 | class ProcessPromise<T> extends Promise<T> { |
举个例子:
1 | try { |
结果:
cd()
cd()
可用于更改当前工作目录:
1 | // 更改当前的工作目录为: src 目录 |
结果:
fetch()
相当于 node
的 node-fetch 包。 用于网络请求。
1 | let resp = await fetch('https://www.fastmock.site/mock/31e59cb8bdba6b63482fd4e6914b76f2/borrow/getName') |
结果:
question()
是对 Node Js
的 readline 包的包装。它的作用是可以提示用户输入。
1 | let name = await question('请输入你的名字: ') |
说是 question 函数的第二个参数是选项。但是没有试出来, 还没找到原因。
sleep()
使用 setTimeout 实现的一个等待函数。
1 | let name = await question('请输入你的名字: ') |
nothrow()
捕捉 $
执行命令时遇到的非 0 返回值,使其不抛异常。一般来说,Shell
编程中Exit Code
不为0代表有异常
1 | try { |
举个例子:
该例子中,开始没有使用 nothrow
函数,执行命令行遇到非0返回值是会抛出错误的。 之后使用 nothrow
函数, 执行,执行命令行遇到非0返回值则不会抛出错误。
内置的全局变量
chalk
即 chalk
包,用于输出彩色的内容
1 | console.log(chalk.green('绿色')); |
更多的使用可看: chalk
fs
引用的 fs-extra 包,用于完成常见的文件操作。
1 | fs.copy('./copy.txt', 'copy1.txt') |
结果:
例子中复制了名为 copy.txt
文件, 复制后的文件名为:copy1.txt
。
globby
引用的 globby 包,用于模糊搜索文件名
1 | const paths = await globby(['*', '!cake']); |
os
引用的 os 包,用于获取系统信息
1 | const type = os.type(); |
path
引用的 path 包,用于对路径做处理。
1 | const file = path.basename("/foo/bar/baz/asdf/shuliqi.txt") |
minimist
引用的 minimist 包,用于处理命令行参数。